/*
 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/**
 * @test
 * @bug 8218021
 * @summary Have jarsigner preserve posix permission attributes
 * @modules jdk.jartool/sun.security.tools.jarsigner
 *          java.base/sun.security.tools.keytool
 * @library /test/lib
 * @run main/othervm PosixPermissionsTest
 */

import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import jdk.test.lib.SecurityTools;

public class PosixPermissionsTest {

    private final static int BYTES_PER_ROW = 8;
    private final static String ZIPFILENAME = "8218021-test.zip";
    private final static String JARFILENAME = "8218021-test.jar";
    private final static URI ZIPURI = URI.create("jar:" + Path.of(ZIPFILENAME).toUri());
    private static final String WARNING_MSG = "POSIX file permission and/or symlink " +
        "attributes detected. These attributes are ignored when signing and are not " +
        "protected by the signature.";

    public static void main(String[] args) throws Exception {
        // In JDK 11 we can't create a test zip file with POSIX permissions on the fly as its zipfs has no POSIX support.
        // So we include the bytes of such a file in a static field. The file can be generated by calling main with an
        // argument, using a JDK where zipfs supports POSIX. This will also print out the bytes.
        if (args.length > 0) {
            generateZipFileWithPosixAttributes();
            System.exit(0);
        }

        createFiles();

        // generate key for signing
        SecurityTools.keytool(
                "-genkey",
                "-keyalg", "RSA",
                "-dname", "CN=Coffey, OU=JPG, O=Oracle, L=Santa Clara, ST=California, C=US",
                "-alias", "examplekey",
                "-storepass", "password",
                "-keypass", "password",
                "-keystore", "examplekeystore",
                "-validity", "365")
                .shouldHaveExitValue(0);

        // sign zip file - expect warning
        SecurityTools.jarsigner(
                "-keystore", "examplekeystore",
                "-verbose", ZIPFILENAME,
                "-storepass", "password",
                "-keypass", "password",
                "examplekey")
                .shouldHaveExitValue(0)
                .shouldContain(WARNING_MSG);

        // sign jar file - expect no warning
        SecurityTools.jarsigner(
                "-keystore", "examplekeystore",
                "-verbose", JARFILENAME,
                "-storepass", "password",
                "-keypass", "password",
                "examplekey")
                .shouldHaveExitValue(0)
                .shouldNotContain(WARNING_MSG);

        // verify zip file - expect warning
        SecurityTools.jarsigner(
                "-keystore", "examplekeystore",
                "-storepass", "password",
                "-keypass", "password",
                "-verbose",
                "-verify", ZIPFILENAME)
                .shouldHaveExitValue(0)
                .shouldContain(WARNING_MSG);

        // verify jar file - expect no warning
        SecurityTools.jarsigner(
                "-keystore", "examplekeystore",
                "-storepass", "password",
                "-keypass", "password",
                "-verbose",
                "-verify", JARFILENAME)
                .shouldHaveExitValue(0)
                .shouldNotContain(WARNING_MSG);
    }

    private static void createFiles() throws Exception {
        Files.write(Path.of(ZIPFILENAME), ZIPBYTES);

        // create jar file for testing
        Path file = Path.of("f");
        Files.createFile(file);
        SecurityTools.jar("cf " + JARFILENAME + " " + file);
    }

    private static void generateZipFileWithPosixAttributes() throws Exception {
        Map<String, String> env = new HashMap<>();
        env.put("create", "true");
        env.put("enablePosixFileAttributes", "true");

        Path file = Path.of("f");
        Files.createFile(file);

        try (FileSystem zipfs = FileSystems.newFileSystem(ZIPURI, env)) {
            Files.copy(file,
                    zipfs.getPath(file.toString()),
                    StandardCopyOption.COPY_ATTRIBUTES);
            Files.setPosixFilePermissions(zipfs.getPath(file.toString()),
                    PosixFilePermissions.fromString("------r--"));
        }

        System.out.println("Bytes of " + ZIPFILENAME + ":");
        System.out.println(createByteArray(Files.readAllBytes(Path.of(ZIPFILENAME)), "ZIPBYTES"));
    }

    /**
     * Utility method which takes an byte array and converts to byte array
     * declaration.  For example:
     * <pre>
     *     {@code
     *        var fooJar = Files.readAllBytes(Path.of("foo.jar"));
     *        var result = createByteArray(fooJar, "FOOBYTES");
     *      }
     * </pre>
     * @param bytes A byte array used to create a byte array declaration
     * @param name Name to be used in the byte array declaration
     * @return The formatted byte array declaration
     */
    private static String createByteArray(byte[] bytes, String name) {
        StringBuilder sb = new StringBuilder();
        try (Formatter fmt = new Formatter(sb)) {
            fmt.format("    public final static byte[] %s = {", name);
            for (int i = 0; i < bytes.length; i++) {
                int mod = i % BYTES_PER_ROW;
                if (mod == 0) {
                    fmt.format("%n        ");
                } else {
                    fmt.format(" ");
                }
                fmt.format("(byte)0x%02x", bytes[i]);
                if (i != bytes.length - 1) {
                    fmt.format(",");
                }
            }
            fmt.format("%n    };%n");
        }
        return sb.toString();
    }

    /*
     * The byte array representation was generated using the createByteArray
     * utility method with a higher JDK:
     * $ <jdk15>/bin/java PosixPermissionsTest generate
     */
    public final static byte[] ZIPBYTES = {
        (byte)0x50, (byte)0x4b, (byte)0x03, (byte)0x04, (byte)0x14, (byte)0x00, (byte)0x08, (byte)0x08,
        (byte)0x08, (byte)0x00, (byte)0x4b, (byte)0x9e, (byte)0x6a, (byte)0x51, (byte)0x00, (byte)0x00,
        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
        (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x24, (byte)0x00, (byte)0x66, (byte)0x0a,
        (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01,
        (byte)0x00, (byte)0x18, (byte)0x00, (byte)0x40, (byte)0xdb, (byte)0xfb, (byte)0x57, (byte)0x92,
        (byte)0xb7, (byte)0xd6, (byte)0x01, (byte)0x40, (byte)0xdb, (byte)0xfb, (byte)0x57, (byte)0x92,
        (byte)0xb7, (byte)0xd6, (byte)0x01, (byte)0x40, (byte)0xdb, (byte)0xfb, (byte)0x57, (byte)0x92,
        (byte)0xb7, (byte)0xd6, (byte)0x01, (byte)0x03, (byte)0x00, (byte)0x50, (byte)0x4b, (byte)0x07,
        (byte)0x08, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x00, (byte)0x00,
        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x50, (byte)0x4b, (byte)0x01,
        (byte)0x02, (byte)0x14, (byte)0x03, (byte)0x14, (byte)0x00, (byte)0x08, (byte)0x08, (byte)0x08,
        (byte)0x00, (byte)0x4b, (byte)0x9e, (byte)0x6a, (byte)0x51, (byte)0x00, (byte)0x00, (byte)0x00,
        (byte)0x00, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
        (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x24, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x04, (byte)0x00, (byte)0x00,
        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x66, (byte)0x0a, (byte)0x00, (byte)0x20, (byte)0x00,
        (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x18, (byte)0x00,
        (byte)0x40, (byte)0xdb, (byte)0xfb, (byte)0x57, (byte)0x92, (byte)0xb7, (byte)0xd6, (byte)0x01,
        (byte)0x40, (byte)0xdb, (byte)0xfb, (byte)0x57, (byte)0x92, (byte)0xb7, (byte)0xd6, (byte)0x01,
        (byte)0x40, (byte)0xdb, (byte)0xfb, (byte)0x57, (byte)0x92, (byte)0xb7, (byte)0xd6, (byte)0x01,
        (byte)0x50, (byte)0x4b, (byte)0x05, (byte)0x06, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
        (byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x00, (byte)0x00,
        (byte)0x55, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00
    };
}
